﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AForge.Neuro;
using AForge.Neuro.Learning;

namespace HAKGERSoft.Synapse.Samples
{
    public class KohonenNetwork : INeuralNetwork
    {
        public DistanceNetwork network;
        private SOMLearning teacher;
        private int cyclesNo;
        private double nChangingRate;
        private double lChangingRate;
        private double[][] input;
        private int networkWidth;

        public KohonenNetwork(int inputsCount, int width, int height, int cyclesNo, int neighborhood, double nChangingRate, double learningRate, double lChangingRate)
        {
            network = new DistanceNetwork(inputsCount, width * height);
            teacher = new SOMLearning(network, width, height)
            {
                LearningRadius = neighborhood,
                LearningRate = learningRate
            };
            this.cyclesNo = cyclesNo;
            this.nChangingRate = nChangingRate;
            this.lChangingRate = lChangingRate;
            networkWidth = width;
        }

        public void LoadTrainingData(string trainingData)
        {
            throw new NotImplementedException();
        }

        public void LoadInput(string intput)
        {
            throw new NotImplementedException();
        }

        public void Learn(bool showErrors, bool saveToFile)
        {
            //System.IO.StreamWriter file = new System.IO.StreamWriter("leariningLog.txt"); ;
            for (int i = 0; i < cyclesNo; i++)
            {
                double error = teacher.RunEpoch(input);

                //teacher.LearningRate = learningRate*lChangingRate*(-1/((neighborhoodSize*nChangingRate) + 1));
                teacher.LearningRadius *= nChangingRate;
                teacher.LearningRate *= lChangingRate;
                if (showErrors)
                {
                    Console.WriteLine("Iteration: {0} squared error value: {1}", i, error / input.Length);
                }
                if (saveToFile)
                {
              //      file.WriteLine("Iteration: {0} squared error value: {1}", i, error / input.Length);
                }
            }
            //file.Close();
        }

        public double[][] ShowInput()
        {
            throw new NotImplementedException();
        }

        public double[][] Run(double[][] input)
        {
            throw new NotImplementedException();
        }

        public double[][] Run()
        {
            List<double[]> outputList = new List<double[]>();

            for (int i = 0; i < input.Length; i++)
            {
                network.Compute(input[i]);
                int winner = network.GetWinner();
                double[] output = new double[network[0][winner].InputsCount];

                for (int j = 0; j < output.Length; j++)
                {
                    output[j] = network[0][winner][j];
                }

                outputList.Add(output);
            }
            return outputList.ToArray();
        }

        public void PrintInputOutput(double[][] input, double[][] output)
        {
            throw new NotImplementedException();
        }

        public void PrintWeights()
        {
            throw new NotImplementedException();
        }

        public void SetInput(double[][] input)
        {
            this.input = input;
        }

        public List<TrainingItem> CreateOutputImages(List<TrainingItem> SomTrainingSet, int windowSize, int step)
        {
            //TODO:
            var ret = new List<TrainingItem>();
            int pictureSize = (int) (Math.Sqrt(SomTrainingSet[0].Image.Length));
            int outputImageSize = (pictureSize/step)-1;
            int outputVectorSize = outputImageSize*outputImageSize*2;
            for (int z = 0; z < SomTrainingSet.Count; z++ )
            {
                var trainingItem = SomTrainingSet[z];
                double[] retImage = new double[outputVectorSize];
                //przesuwac okno po obrazku 
                for (int l = 0; l < pictureSize - step; l += step)
                {
                    for (int k = 0; k < pictureSize - step; k += step)
                    {
                        double[] windowVector = new double[windowSize * windowSize];
                        for (int i = 0; i < windowSize; i++)
                        {
                            for (int j = 0; j < windowSize; j++)
                            {
                                windowVector[i * windowSize + j] = trainingItem.Image[l * k + k + (pictureSize * i + j)];
                            }
                        }
                        network.Compute(windowVector);
                        int winner = network.GetWinner();
                        int wx = winner % networkWidth;
                        int wy = (int)((double)winner / (double)networkWidth);
                        retImage[(l/step)*(k/step) + k/step] = Normalize(wx);
                        retImage[outputImageSize + (l / step) * (k / step) + k / step] = Normalize(wy);
                        }
                }
                ret.Add(new TrainingItem(trainingItem.ClassNumber,retImage));
            }
            return ret;
        }

        private double Normalize(int winner)
        {
            return (double)winner / 2 - 1;
        }
    }
}
